
#include <BSocket>
#include <BLibrary>
#include <BReadWrite>
#include "IGlobal.h"
#include "ISockets.h"
#include "member_BObject.h"
#include "member_BWidget.h"
#include "member_BDrag.h"
#include "member_BStyle.h"
#include "member_BPixmap.h"
#include "member_BScene.h"
#include "member_BMovie.h"
#include "member_BAudioInput.h"
#include "member_BAudioOutput.h"
#include "member_BFolder.h"
#include "member_BShortcut.h"

using namespace BWE;

int				app_argc = 0;
BStringArray	app_argv = 0;
BReal			app_tick = 0;
bool			app_done = false;
int				app_code = 0;
int				app_frequency = 30;
int 			app_realFrequency = 0;
BReal			app_beginTime = BReal(0);
BReal			app_lastRenderTime = BReal(0);
BReal			app_clickInterval = BReal(0.5);
BReal			app_tipsDelay = BReal(1);
BWidget*		app_coreWidget = 0;
BValue			app_clipBoard;

#ifdef linux
pthread_t		app_thread_id = 0;
Display*		app_display = 0;
#endif

#ifdef _WIN32
#undef near
#undef far
DWORD			app_thread_id = 0;
#endif

BString				app_appPath;
BString				app_libPath;
BString				app_homePath;
BArray<BString>		app_fontPaths;
BArray<BString>		app_pluginPaths;

BHolder<BFont>			app_font;
BHolder<BStyle>			app_style;
BList<IWindowHolder>	app_windows;
BList<BThread*>			app_threads;
BList<BSocket*>			app_sockets;
BList<BScene*>			app_scenes;
BList<BMovie*>			app_movies;
BList<BShortcut*>		app_shortcuts;
BList<BAudioInput*>		app_audioInputs;
BList<BAudioOutput*>	app_audioOutputs;
BSet<BFolder*>			app_foldersNotifying;

BList<bplugin>				app_plugins;
BMap<BString, BString>		app_extAlias;
BMap<BString, IResource>	app_resources;
BMap<BString, BValue>		app_propertys;

#ifdef linux
pthread_mutex_t		app_mutex;
pthread_mutex_t		app_signal_mutex;
#else
CRITICAL_SECTION	app_section;
CRITICAL_SECTION	app_signal_section;
#endif
BMap<BObject*, SlotPackArray>	app_slotPackMap;
BMap<BObject*, SignalWaitArray>	app_signalWaitMap;

bool App_InitGlobalMutex()
{
#ifdef linux
	pthread_mutexattr_t attr;
	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
	int error = pthread_mutex_init(&app_signal_mutex, &attr);
#endif
#ifdef _WIN32
	InitializeCriticalSection(&app_signal_section);
#endif
	return true;
}
bool App_LockGlobalMutex()
{
	if (app_done)
		return false;
#ifdef linux
	int error = pthread_mutex_lock(&app_signal_mutex);
	return error == 0;
#else
	EnterCriticalSection(&app_signal_section);
	return true;
#endif
}
bool App_UnlockGlobalMutex()
{
	if (app_done)
		return false;
#ifdef linux
	int error = pthread_mutex_unlock(&app_signal_mutex);
	return error == 0;
#else
	LeaveCriticalSection(&app_signal_section);
	return true;
#endif
}
bool App_CheckNotifying()
{
	if (app_done)
		return false;
	App_LockGlobalMutex();
#ifdef linux
	for (int i = 0; i < app_foldersNotifying.size(); i++)
	{
		BFolder* folder = app_foldersNotifying[i];
		const BString& path = folder_member(folder)->path;
		if (!folder_member(folder)->notifying)
		{
			//for (auto it = folder_member(folder)->handlesMap.begin(); it.valid(); ++it)
			//{
			//	member.nty = inotify_init();
			//	if (-1 == inotify_add_watch(member.nty, member.path.cstr(), IN_ALL_EVENTS))
			//	{
			//		perror("inotify_add_watch");
			//	}
			//	continue;
			//}
		}
		else
		{
				app_foldersNotifying.remove(folder);
		}
	}
#else
	for (int i = 0; i < app_foldersNotifying.size(); i++)
	{
		BFolder* folder = app_foldersNotifying[i];
		const BString& path = folder_member(folder)->path;
		if (!folder_member(folder)->notifying)
		{
			for (auto it = folder_member(folder)->handlesMap.begin(); it.valid(); ++it)
			{
				HANDLE handle = *it;
				if (handle != INVALID_HANDLE_VALUE)
				{
					FindCloseChangeNotification(handle);
				}
			}
			folder_member(folder)->handlesMap.clear();
			app_foldersNotifying.remove(i--);
			continue;
		}
		else
		{
			if (folder_member(folder)->handlesMap.empty())
			{
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_FILE_NAME] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_DIR_NAME] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_ATTRIBUTES] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_SIZE] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_LAST_WRITE] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_LAST_ACCESS] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_CREATION] = INVALID_HANDLE_VALUE;
				folder_member(folder)->handlesMap[FILE_NOTIFY_CHANGE_SECURITY] = INVALID_HANDLE_VALUE;
			}
		}
		for (auto it = folder_member(folder)->handlesMap.begin(); it.valid(); ++it)
		{
			int filter = it.key();
			HANDLE handle = it.value();
			if (handle == INVALID_HANDLE_VALUE)
			{
				handle = FindFirstChangeNotificationA(path.cstr(), TRUE, filter);
				folder_member(folder)->handlesMap[filter] = handle;
				continue;
			}
			DWORD ressult = WaitForSingleObject(handle, 0);
			if (ressult == WAIT_OBJECT_0)
			{
				Notify notify = Notify_None;
				switch (filter)
				{
				case FILE_NOTIFY_CHANGE_FILE_NAME:		notify = Notify_Rename;		break;
				case FILE_NOTIFY_CHANGE_DIR_NAME:		notify = Notify_Rename;		break;
				case FILE_NOTIFY_CHANGE_ATTRIBUTES:		notify = Notify_Attribs;	break;
				case FILE_NOTIFY_CHANGE_SIZE:			notify = Notify_Resize;		break;
				case FILE_NOTIFY_CHANGE_LAST_WRITE:		notify = Notify_Write;		break;
				case FILE_NOTIFY_CHANGE_LAST_ACCESS:	notify = Notify_Access;		break;
				case FILE_NOTIFY_CHANGE_CREATION:		notify = Notify_Creation;	break;
				case FILE_NOTIFY_CHANGE_SECURITY:		notify = Notify_Security;	break;
				}
				object_member(folder)->emit(Signal_Notifying, notify);
				if (!FindNextChangeNotification(handle))
				{
					printf("Find next change notification : %s.\n", path.cstr());
				}
			}
			if (ressult == WAIT_FAILED)
			{
				printf("Wait for notify change failed : %s.\n", path.cstr());
			}
		}
	}
#endif
	App_UnlockGlobalMutex();
	return true;
}

bool App_AttachWindow(BWidget* widget)
{
#ifdef linux
	if (!app_display)
		return false;
#endif
	if (!widget)
		return false;
	IWindow* window = 0;
	for (int i = 0; i < app_windows.size(); i++)
	{
		IWindow* w = app_windows[i];
		if (w->boss == 0)
		{
			window = app_windows[i];
			break;
		}
	}
	if (!window)
	{
		window = new IWindow();
		app_windows.append(window);
	}
	window->reset(widget);
	widget_member(widget)->window = window;
	widget_member(widget)->dirtyStyle = true;
	app_done = false;
	return true;
}
bool App_DetachWindow(BWidget* widget)
{
	if (!widget)
		return false;
	if (app_coreWidget == widget)
		app_coreWidget = 0;
	if (IWindow* window = widget_member(widget)->window)
	{
		window->boss = 0;
		window->shown = false;
		window->close_time = bTime();

		window->hoverCursor = 0;
		window->hoverWidget = 0;
		window->focusWidget = 0;
		window->popupWidgets.clear();
		window->keyStates.fill(false);
		window->tipsBoard.reset(0);
		window->dragBoard.reset(0);

		window->hideWindow();
		widget_member(widget)->window = 0;
		app_windows.lower(window);
	}
	return true;
}

void App_CycleBegin()
{
	app_beginTime = bTime();
}
void App_Event(IWindow* window)
{
#ifdef linux
	Display* display = app_display;
	if (window)
		display = window->display;
	XEvent event;
	while (XPending(display))
	{
		XNextEvent(display, &event);
		const XAnyEvent& any = (const XAnyEvent&)event;
		if (window)
		{
			if (any.window == window->hwnd)
			{
				window->procEvent(event);
			}
		}
		else
		{
			for (int i = 0; i < app_windows.size(); i++)
			{
				IWindow* win = app_windows[i];
				if (any.window == win->hwnd)
				{
					win->procEvent(event);
					break;
				}
			}
		}
	}
#endif
#ifdef _WIN32
	MSG msg;
	if (window)
	{
		while (PeekMessage(&msg, window->hwnd, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	else
	{
		while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
#endif
}
void App_Event(BMouseEvent& mouseEvent)
{
	BDrag* drag = mouseEvent.value();
#ifdef linux
	
#endif
	
#ifdef _WIN32
	MSG msg;
	while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
	{
		if (msg.message == 0)
			break;
		if (msg.message == WM_QUIT)
			break;
		TranslateMessage(&msg);
		IWindow* window = 0;
		for (int i = 0; i < app_windows.size(); i++)
		{
			if (app_windows[i]->hwnd == msg.hwnd)
			{
				window = app_windows[i];
				break;
			}
		}
		if (window)
		{
			if (msg.message == WM_LBUTTONUP)
			{
				mouseEvent.setType(Event_DragDrop);
				window->procDragDrop(mouseEvent);
				drag_member(drag)->over = true;
				//DispatchMessage(&msg);
				break;
			}
			if (msg.message == WM_MOUSEMOVE)
			{
				BPoint absolutePos((short)LOWORD(msg.lParam), (short)HIWORD(msg.lParam));
				mouseEvent.setAbsolutePos(absolutePos);
				window->procDragMove(mouseEvent);
				//DispatchMessage(&msg);
			}
		}
		else
		{
			DispatchMessage(&msg);
		}
	}
#endif
}
void App_Signal()
{
	while (!app_done && app_slotPackMap.size())
	{
		App_LockGlobalMutex();
		auto it = app_slotPackMap.begin();
		BObject* object = it.key();
		SlotPackArray slotPacks = it.value();
		app_slotPackMap.erase(it);
		App_UnlockGlobalMutex();
		for (int i = 0; i < slotPacks.size(); i++)
		{
			SlotPack& pack = slotPacks[i];
			if (pack.object)
			{
				if (pack.signal)
					object_member(object)->post(pack.object, pack.signal, pack.value);
				if (pack.strsig.size())
					object_member(object)->post(pack.object, pack.strsig, pack.value);
			}
			else
			{
				if (pack.signal)
					object_member(object)->emit(pack.signal, pack.value);
				if (pack.strsig.size())
					object_member(object)->emit(pack.strsig, pack.value);
			}
		}
	}
}
void App_Update()
{
	if (app_done)
		return;
	for (int i = 0; i < app_audioInputs.size(); i++)
	{
		BAudioInput* audioInput = app_audioInputs[i];
		audioinput_member(audioInput)->emitSignals();
	}
	BReal time = bTime();
	for (int i = 0; i < app_scenes.size(); i++)
	{
		BScene* scene = app_scenes[i];
		if (scene->enabled())
		{
			scene->lock();
			scene_member(scene)->update(time);
			scene->unlock();
		}
	}
	for (int i = 0; i < app_movies.size(); i++)
	{
		BMovie* movie = app_movies[i];
		if (movie->enabled())
		{
			movie->lock();
			movie_member(movie)->update(time);
			movie->unlock();
		}
	}
	for (int i = 0; i < app_windows.size(); i++)
	{
		IWindow* window = app_windows[i];
		if (!window->boss)
			continue;
		if (window->boss->enabled())
		{
			window->update();
		}
	}
}
void App_Frame()
{
	if (app_done || app_windows.empty())
		return;
	for (int i = 0; i < app_windows.size(); i++)
	{
		IWindow* window = app_windows[i];
		if (!window->boss)
			continue;
		if (window->boss->enabled() && window->boss->visible())
		{
			window->fresh();
			window->frame();
		}
	}
}
void App_CycleEnd(bool sleep)
{
	if (app_done)
		return;

	static int frameCount = 0;
	frameCount++;
	BReal now = bTime();
	if (now - app_tick >= 1)
	{
		app_tick = now;
		app_realFrequency = frameCount;
		frameCount = 0;
	}

	for (int i = 0; i < app_windows.size(); i++)
	{
		IWindow* window = app_windows[i];
		if (!window->boss && now - window->close_time > 600)
		{
			app_windows.remove(i--);
		}
	}

	BReal duration = BReal(1) / app_frequency;
	BReal timePass = now - app_beginTime;
	BReal restTime = duration - timePass;

	App_SelectSocket(restTime);
	App_CheckNotifying();

	if (sleep)
	{
		BReal timePass = bTime() - app_beginTime;
		BReal sleepTime = duration - timePass;
		if (sleepTime > 0)
		{
			bSleep(sleepTime);
		}
	}
}
